#include "Bucket.h"

Bucket* CrearBucket(unsigned int tamanioBucket)
{
	Bucket* nuevoBucket = (Bucket*) malloc(sizeof(Bucket));
	nuevoBucket->bytesLibres = tamanioBucket - sizeof(unsigned int) - sizeof (unsigned int);
											//siempre se va a guardar la dispersion y la cantidad de elementos
	nuevoBucket->cantElementos = 0;
	nuevoBucket->elementos = NULL;
	nuevoBucket->tamDispersion = 0;
	nuevoBucket->tamanioBucket = tamanioBucket;
	nuevoBucket->longitudElemento = NULL;
	nuevoBucket->nroBucket = -1;

	return nuevoBucket;
}

void* ObtenerElementoBucket (Bucket bucket, TClave clave, unsigned int * tamanioElem)
{
	int i;
	for (i = 0; i < bucket.cantElementos; i++)
	{
		if (strcmp(bucket.elementos[i].clave,clave) == 0)
		{
			void * elem = (void*) malloc(bucket.longitudElemento[i]);
			memcpy(elem, bucket.elementos[i].dato, bucket.longitudElemento[i]);
			*tamanioElem = bucket.longitudElemento[i];
			return elem;
		}
	}
	return NULL;
}

int BuscarElementoBucket(Bucket* bucket, TClave clave)
{
	int i;
	if(bucket->cantElementos>0)
	{
		for(i=0;i<bucket->cantElementos;i++)
		{
			if(strcmp(bucket->elementos[i].clave,clave) == 0)
			{
				return OK;
			}
		}
	}
	return ERROR;
}

int InsertarElementoBucket (Bucket* bucket, TClave clave, void* elementoDato, unsigned short int longitudElemento)
{
	int long_clave = (strlen(clave)+1);
	/* realoco memoria en el bucket, para agregar la longitud del nuevo elemento, y el elemento.*/
	bucket->longitudElemento = (TLongElem*) realloc(bucket->longitudElemento,(sizeof(TLongElem)) * (bucket->cantElementos + 1 ));

	bucket->elementos = (Elemento*) realloc(bucket->elementos,sizeof (Elemento) * (bucket->cantElementos + 1));

	/* agrego la clave del nuevo elemento */
	bucket->elementos[bucket->cantElementos].clave = (char*)malloc(sizeof(char)*long_clave); // +1 por el '\0'.
	strcpy(bucket->elementos[bucket->cantElementos].clave,clave);
	/* agrego la longitud del nuevo elemento */
	bucket->longitudElemento[bucket->cantElementos] = longitudElemento;
	/* copio el elemento en el bucket */

	bucket->elementos[bucket->cantElementos].dato = (void*) malloc (longitudElemento);
	memcpy(bucket->elementos[bucket->cantElementos].dato, elementoDato, longitudElemento);

	/* incremento la cantidad de elementos */
	bucket->cantElementos++;
	/* resto la cantidad de bytes q se ocuparon a los BytesLibres */
	bucket->bytesLibres -= (sizeof(char)*long_clave) + sizeof (TLongElem) + longitudElemento;

	return OK;

}


void liberarElementos(Elemento* elementos, unsigned int cantidad) {
	int i;
	for (i=0;i<cantidad;i++){
		free(elementos[i].clave);
		free(elementos[i].dato);
	}
}

int EliminarElementoBucket(Bucket* bucket, TClave clave)
{
	/* Si el elemento no existe devuelvo Error*/
	if(BuscarElementoBucket(bucket,clave)== ERROR)
	{
		return ERROR;
	}
	else
	{
		unsigned int clave_long = (strlen(clave)+1);
		Elemento* elementosAux = (Elemento*) malloc (sizeof (Elemento) * (bucket->cantElementos - 1));
		TLongElem* tamanioElementoAux = (TLongElem*) malloc( (sizeof(TLongElem)) * (bucket->cantElementos -1));
		int i = 0;

		while(strcmp(bucket->elementos[i].clave,clave) != 0)
		{
			unsigned int longitud = strlen(bucket->elementos[i].clave) + 1;
			elementosAux[i].clave = (TClave) malloc(sizeof(char)*longitud);
			memcpy(elementosAux[i].clave, bucket->elementos[i].clave,sizeof(char)*longitud);
			elementosAux[i].dato = (void*) malloc (bucket->longitudElemento[i]);
			memcpy(elementosAux[i].dato, bucket->elementos[i].dato,bucket->longitudElemento[i]);
			memcpy(&tamanioElementoAux[i], &bucket->longitudElemento[i],sizeof(TLongElem));
			i++;
		}

		//Actualizo Bytes Libres
		bucket->bytesLibres += (sizeof(TLongElem) + (sizeof(char)*clave_long) + bucket->longitudElemento[i]);

		//ya copie hasta antes del que tengo que remover ahora copio el resto
		for( ;i<bucket->cantElementos - 1; i++)
		{
			unsigned int longitud = strlen(bucket->elementos[i+1].clave) + 1;
			elementosAux[i].clave = (TClave) malloc(sizeof(char)*longitud);
			memcpy(elementosAux[i].clave, bucket->elementos[i+1].clave,sizeof(char)*longitud);
			elementosAux[i].dato = (void*) malloc (bucket->longitudElemento[i]);
			memcpy(elementosAux[i].dato, bucket->elementos[i+1].dato, bucket->longitudElemento[i]);
			memcpy(&tamanioElementoAux[i], &bucket->longitudElemento[i+1],sizeof(TLongElem));
		}

		liberarElementos(bucket->elementos,bucket->cantElementos);
		free(bucket->elementos);
		free(bucket->longitudElemento);

		bucket->cantElementos--;
		bucket->elementos = elementosAux;
		bucket->longitudElemento = tamanioElementoAux;

		return OK;
	}
}

int ModificarElementoBucket(Bucket* bucket,TClave clave,void* elemento,unsigned int longElemento)
{
	unsigned int i = 0;

	while(strcmp(bucket->elementos[i].clave,clave) != 0)
	{
		i++;
	}
	if(strcmp(bucket->elementos[i].clave,clave) == 0)
	{
		free(bucket->elementos[i].dato);
		bucket->elementos[i].dato = (void*) malloc (longElemento);
		memcpy(bucket->elementos[i].dato,elemento,longElemento);
		bucket->longitudElemento[i] = longElemento;
		bucket->bytesLibres -= longElemento;
		return OK;
	}
	return ERROR;
}


void LiberarBucket (Bucket* bucket)
{
	int i;
	if(bucket->cantElementos>0)
	{
		for (i=bucket->cantElementos -1 ;i>=0;i--)
		{
			free (bucket->elementos[i].clave);
			free (bucket->elementos[i].dato);
			bucket->elementos[i].dato = NULL;
		}
	}

	free (bucket->longitudElemento);
	bucket->longitudElemento = NULL;
	free (bucket->elementos);
	bucket->elementos = NULL;
	free(bucket);
	bucket = NULL;
}

void BucketABytes (Bucket* bucket, char* bucketPersistente)
{

	// [cantElem][disp][clave1][...][claveN][long1][...][longN][elem1][...][elemN]

	unsigned int longitud;
	unsigned int actual=0;

	// Copio la cantidad de elementos que tiene
	longitud = sizeof(unsigned int);
	memcpy(&bucketPersistente[actual],&bucket->cantElementos,longitud);

	// Copio el tamanio de dispersion del bucket
	actual += longitud;
	longitud = sizeof(unsigned int);
	memcpy(&bucketPersistente[actual], &bucket->tamDispersion,longitud);

	actual += longitud;
	// Copio las claves de los elementos
	int i;
	for (i=0; i < bucket->cantElementos; i++)
	{
		longitud = sizeof(char) * (strlen(bucket->elementos[i].clave)+1);
		memcpy(&bucketPersistente[actual],bucket->elementos[i].clave,longitud);
		actual+=longitud;
	}

	// Copio la longitud de cada dato
	for (i=0; i < bucket->cantElementos; i++)
	{
		longitud = sizeof(TLongElem);
		memcpy(&bucketPersistente[actual],&bucket->longitudElemento[i],longitud);
		actual+=longitud;
	}

	// Copio los datos
	for (i=0; i < bucket->cantElementos; i++)
	{
		memcpy(&bucketPersistente[actual], bucket->elementos[i].dato, bucket->longitudElemento[i]);
		actual+=bucket->longitudElemento[i];
	}

	//Completo con espacios para que no quede basura con caracteres no escribibles.
	memset(&bucketPersistente[actual],' ',bucket->tamanioBucket - actual);
}

int GuardarBucket (FILE* archBloques, Bucket* bucket)
{
	if (bucket->nroBucket == -1) return ERROR;

	char* bloque = (char*) malloc (sizeof(char)*bucket->tamanioBucket);

	int offset = bucket->nroBucket * bucket->tamanioBucket;
	BucketABytes(bucket, bloque);

	fseek(archBloques, offset, SEEK_SET);

	int verif = fwrite(bloque, bucket->tamanioBucket, 1, archBloques);

	free(bloque);
	if(verif == 1)
	{
		fflush(archBloques);
		return OK;
	}

	return ERROR;
}


void CargarBucket(Bucket* nuevoBucket, char* bucket, unsigned int nroBucket)
{
	unsigned int longitud;
	unsigned int actual=0;
	unsigned int i;

	//Copio el numero de Bucket
	nuevoBucket->nroBucket= nroBucket;

	// Copio la cantidad de elementos que tiene
	longitud = sizeof(unsigned int);
	memcpy(&nuevoBucket->cantElementos,&bucket[actual],longitud);

	// Copio el tamanio de dispersion del bucket
	actual += longitud;
	longitud = sizeof(unsigned int);
	memcpy(&nuevoBucket->tamDispersion,&bucket[actual],longitud);

	//Reservo Memoria para los vectores
	nuevoBucket->elementos = (Elemento*) malloc ( sizeof(Elemento) * nuevoBucket->cantElementos);
	nuevoBucket->longitudElemento = (TLongElem*) malloc( sizeof(TLongElem) * nuevoBucket->cantElementos);

	actual+=longitud;
	// Copio las claves
	for (i=0;i<nuevoBucket->cantElementos;i++)
	{
		int j = actual;
		while(bucket[j]!='\0')
		{
			j++;
		}
		if(bucket[j]=='\0')
		{
			longitud = j-actual+1;
			nuevoBucket->elementos[i].clave = (TClave) malloc(sizeof(char)*longitud);
			memcpy(nuevoBucket->elementos[i].clave,&bucket[actual],longitud);
			actual+=longitud;
		}
	}

	//Copio la longitud de cada dato
	for (i=0;i<nuevoBucket->cantElementos;i++)
	{
		longitud = sizeof(TLongElem);
		memcpy(&nuevoBucket->longitudElemento[i],&bucket[actual],longitud);
		actual+=longitud;
	}

	// Copio los datos
	for (i=0; i < nuevoBucket->cantElementos; i++)
	{
		nuevoBucket->elementos[i].dato = (void*) malloc(nuevoBucket->longitudElemento[i]);
		memcpy(nuevoBucket->elementos[i].dato,&bucket[actual], nuevoBucket->longitudElemento[i]);
		actual+=nuevoBucket->longitudElemento[i];
	}

	// Guardo la cantidad de bytes libres del bucket.
	nuevoBucket->bytesLibres = nuevoBucket->tamanioBucket - actual;

}

/* Aloca la memoria necesaria, levanta el bloque del archivo y lo devuelve.*/
Bucket* LevantarBucket (FILE* archBloques, unsigned int nroBucket, unsigned int tamanioBucket)
{
	char bloque[tamanioBucket];
	//bloque = (char*) malloc(sizeof(char) * tamanioBloque);

	// Obtengo la posicion donde se encuentra el bloque necesitado
	unsigned int offset  = nroBucket*tamanioBucket;

	unsigned int resultado = fseek (archBloques, offset, SEEK_SET);
	if (resultado != 0) return NULL;
	int verif = fread (bloque, tamanioBucket, 1, archBloques);
	if(verif == 1)
	{
		Bucket* bucket = CrearBucket(tamanioBucket);
		CargarBucket(bucket, bloque, nroBucket);
		return bucket;
	}
	return NULL;
}

int VerificarEspacioLibreEnBucket (Bucket bucket, TClave clave, unsigned int longElemento)
{
	//Verificar que no se pase del 80% lleno
	int long_clave = (sizeof(char)*(strlen(clave)+1));
	int aOcupar = bucket.bytesLibres - longElemento - long_clave - sizeof(TLongElem);
	int porcentajeLibre = bucket.tamanioBucket * 0.2;
	if (aOcupar < porcentajeLibre)
		return ERROR;
	return OK;
}

void* ObtenerElementoByIndice(Bucket* bucket, unsigned int indice, TClave* claveDevuelta)
{
	if (indice >= bucket->cantElementos)
	{
		*claveDevuelta = NULL;
		return NULL;
	}

	int longClave = strlen(bucket->elementos[indice].clave);
	*claveDevuelta = (TClave) malloc(sizeof(char)*(longClave + 1));
	memcpy(*claveDevuelta, bucket->elementos[indice].clave, (longClave+1));

	void * elem = (void*) malloc(bucket->longitudElemento[indice]);
	memcpy(elem, bucket->elementos[indice].dato, bucket->longitudElemento[indice]);
	return elem;
}


void visualizarBucket(Bucket* bucket, FILE* salida)
{
	int i;

	fprintf(salida,"	%d|(%d) ",bucket->nroBucket,bucket->tamDispersion);

	if(bucket->cantElementos>0)
	{
		for(i=0;i<bucket->cantElementos-1;i++)
		{
			fprintf(salida,"%s, ", bucket->elementos[i].clave);
		}
		fprintf(salida,"%s", bucket->elementos[i].clave);
	}
	fprintf(salida,"\n");

}

void visualizarElementosBucket(Bucket* bucket, FILE* salida)
{
	int i;

	if(bucket->cantElementos>0)
	{
		for(i=0;i<bucket->cantElementos;i++)
		{
			fprintf(salida,"%s: ", bucket->elementos[i].clave);
			char* elemento = (char*) malloc (sizeof(char)*bucket->longitudElemento[i]);
			memcpy(elemento,bucket->elementos[i].dato,bucket->longitudElemento[i]);
			fprintf(salida,"%s\n",elemento);
			free(elemento);
		}
	}
}
